﻿/****************************************************************
 * Developed by Embedded System Group (GSE) - PUCRS - Brasil	*
 * Developers/Warriors:						*
 * 	Cristiano Bolla Fernandes				*
 * 	Benito Michelon						*
 ****************************************************************/
using System;
using System.Net;
using System.Net.Sockets;

namespace XCRF500
{
	public class Reader
	{
		public bool connected = false, loop = false;
		public string ip, tag_t = "6C";		/* Using ISO18000-6C tags by default */
		public int port;
		private NetworkStream networkstream;

		/******************************************************************************/
		/* Conncting and Disconnecting..                                              */
		/* If you need explanation for this functions get out and don't waste my time */
		/******************************************************************************/
		public bool Connect(string addr, int portn)
		{
			ip = addr;
			port = portn;
			try
			{
				IPEndPoint ipend = new IPEndPoint(IPAddress.Parse(addr), port);
				Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
				server.Connect(ipend);
				networkstream = new NetworkStream(server);
				ConfigReaderSys();
				connected = true;
				return true;

			}
			catch
			{
				return false;
			}
		}
		public bool Disconnect()
		{
			try
			{
				if (connected)
				{
					PowerAmplifier("off");
					networkstream.Close();
					connected = false;
				}
				return true;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		/*********************/
		/* Abstraction layer */
		/*********************/
		public bool SetReadLoop(byte antenna)
		{
			switch (tag_t)
			{
				case "6C":
					return SetReadLoop_6C(antenna);
				case "6B":
					return SetReadLoop_6B(antenna);
				default:
					return false;
			}
		}
		public string Read()
		{
			switch (tag_t)
			{
				case "6C":
					return Read_6C();
				case "6B":
					return Read_6B();
				default:
					return null;
			}
		}
		public string Read(int num, byte antenna)
		{
			switch (tag_t)
			{
				case "6C":
					return Read_6C(num, antenna);
				case "6B":
					return null;
				default:
					return null;
			}
		}
		public bool Write(string epcn)
		{
			switch (tag_t)
			{
				case "6C":
					return Write_6C(epcn);
				case "6B":
					return false;
				default:
					return false;
			}
		}
		public bool Write(string epcn, string passwd)
		{
			switch (tag_t)
			{
				case "6C":
					return Write_6C(epcn, passwd);
				case "6B":
					return false;
				default:
					return false;
			}
		}
		public string ReadData(string tid, byte startaddr, byte datalength, byte antenna)
		{
			switch (tag_t)
			{
				case "6C":
					return null;
				case "6B":
					return ReadData_6B(tid, startaddr, datalength, antenna);
				default:
					return null;
			}
		}
		public bool WriteData(string tid, byte startaddr, byte antenna, string sdata)
		{
			switch (tag_t)
			{
				case "6C":
					return false;
				case "6B":
					return WriteData_6B(tid, startaddr, antenna, sdata);
				default:
					return false;
			}
		}
		/************************************/
		/* Instructions for ISO18000-6C Tag */
		/************************************/
		private bool SetReadLoop_6C(byte antenna)
		{
			byte[] read_loop = { 0x00, 0x04, 0x81, 0x00, 0x00, 0x01, 0x15, 0xda };

			try
			{
				if (antenna != 0)
				{
					read_loop[3] = antenna;
					Checksum(read_loop, 6);
				}
				networkstream.Write(read_loop, 0, read_loop.Length);
				loop = true;
				return true;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		private string Read_6C()
		{
			byte[] buff = new byte[19];
			byte[] crc = new byte[19];
			try
			{
				if (loop)
				{
					buff = GivemeWhatINeed(0x81);
					/* ERROR CHECK */
					if (buff[3] == 0x00)
					{
						/* Checksum */
						crc = buff;
						Checksum(crc, 17);
						if (Misc.Equals(crc, buff))
							return Misc.PrintTag(buff, 19);
					}
				}
				return null;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		/* TODO */
		private string Read_6C(int num, byte antenna)
		{
			byte[] read_tag = { 0x00, 0x04, 0x81, 0x01, 0x00, 0x00, 0x15, 0xc8 };
			byte[] buff = new byte[num * 19];
			int size;

			try
			{
				if (num > 1)
				{
					read_tag[4] = (byte)solveq(num);	/* Solve Q time slot */
					Checksum(read_tag, 6);			/* Checksum :-) */
				}
				if (antenna != 1)
				{
					read_tag[3] = antenna;
					Checksum(read_tag, 6);
				}
				networkstream.Write(read_tag, 0, read_tag.Length);
				/* Give the method what it needs dude! */
				size = networkstream.Read(buff, 0, buff.Length);
				return buff[3] == 0x00 ? Misc.PrintTag(buff, size) : null;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		private bool Write_6C(string epcn)
		{
			return Write_6C(epcn, "0");
		}
		private bool Write_6C(string epcn, string passwd)
		{
			try
			{
				byte[] write_cmd = { 0x00, 0x13, 0x83, 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
				byte[] aux = new byte[7];

				/* Turns string (epcn) into a 12 bytes hexadecimal byte array (epc) */
				byte[] epc = Misc.GetBytes(epcn, 12);
				byte[] pass = Misc.GetBytes(passwd, 4);

				write_cmd[3] = 0x00;	/* TODO anetnna */
				for (int i = 0; i < epc.Length; i++)
					write_cmd[(21 - epc.Length) + i] = epc[i];
				for (int i = 0; i < pass.Length; i++)
					write_cmd[(8 - pass.Length) + i] = pass[i];
				Checksum(write_cmd, 21);
				networkstream.Write(write_cmd, 0, write_cmd.Length);
				networkstream.Read(aux, 0, aux.Length);
				/* Give the method what it needs dude! */
				return aux[3] == 0x00 ? true : false;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		/************************************/
		/* Instructions for ISO18000-6B Tag */
		/************************************/
		private bool SetReadLoop_6B(byte antenna)
		{
			byte[] read_loop = { 0x00, 0x03, 0x71, 0x00, 0x01, 0xba, 0xd1 };
			try
			{
				if (antenna != 0)
				{
					read_loop[3] = antenna;
					Checksum(read_loop, 5);
				}
				networkstream.Write(read_loop, 0, read_loop.Length);
				loop = true;
				return true;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		private string Read_6B()
		{
			byte[] buff = new byte[15];
			byte[] crc = new byte[15];

			try
			{
				if (loop && networkstream.DataAvailable)
				{
					buff = GivemeWhatINeed(0x71);
					/* ERROR CHECK */
					if (buff[3] == 0)
					{
						/* Checksum */
						crc = buff;
						Checksum(crc, 13);
						return Misc.Equals(crc, buff) ? Misc.PrintTag(buff, 15) : null;
					}
				}
				return null;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		private string ReadData_6B(string tid, byte startaddr, byte datalength, byte antenna)
		{
			byte[] read_data = { 0x00, 0x0c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0xc8 };
			byte[] tagid = new byte[8];
			byte[] buff = new byte[7 + datalength];
			string ax = null;
			int size, i;

			try
			{
				startaddr += 8;
				tagid = Misc.GetBytes(tid, 8);
				read_data[3] = antenna;
				read_data[12] = startaddr;
				read_data[13] = datalength;
				for (i = 0; i < tagid.Length; i++)
					read_data[4 + i] = tagid[i];
				Checksum(read_data, 14);
				networkstream.Write(read_data, 0, read_data.Length);
				buff = GivemeWhatINeed(0x72);
				if (buff[3] == 0)
				{
					// TODO: Use buff[0] and buff[1] to calculate the size
					size = buff[1] + 4; // Size with header

					string tmp = Misc.PrintTag(buff, size).Replace(" ", "");
					if (tmp.Length >= (10 + (datalength * 2)))
						ax = tmp.Substring(10, datalength * 2);
				}
				return ax;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		private bool WriteData_6B(string tid, byte startaddr, byte antenna, string sdata)
		{
			int datalength = (int)System.Math.Ceiling((double)sdata.Length / 2);
			byte[] write_data = new byte[16 + datalength];
			byte[] header = { 0x00, 0x0c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
			byte[] tagid = new byte[8];
			byte[] buff = new byte[7];
			byte[] data;
			int i;

			try
			{
				startaddr += 8;
				tagid = Misc.GetBytes(tid, 8);
				data = Misc.GetBytes(sdata, datalength);
				header[3] = antenna;
				header[12] = startaddr;
				header[13] = (byte)datalength;
				for (i = 0; i < tagid.Length; i++)
					header[4 + i] = tagid[i];
				System.Buffer.BlockCopy(header, 0, write_data, 0, header.Length);
				for (i = 0; i < data.Length; i++)
					write_data[14 + i] = data[i];
				write_data[1] = (byte)(write_data.Length - 4);
				Checksum(write_data, write_data.Length - 2);
				networkstream.Write(write_data, 0, write_data.Length);
				buff = GivemeWhatINeed(0x73);
				return buff[3] == 0x00 ? true : false;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		/************************************/
		/* Reader Config and misc functions */
		/************************************/
		private bool ConfigReaderSys()
		{
			byte[] config_reader_sys = { 0x00, 0x04, 0x63, 0x02, 0x01, 0x04, 0x3b, 0xc1 };
			byte[] buff = new byte[6];

			try
			{
				networkstream.Write(config_reader_sys, 0, config_reader_sys.Length);
				buff = GivemeWhatINeed(0x63);
				return buff[3] == 0x00 ? true : false;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		public bool PowerAmplifier(string onoff)
		{
			return PowerAmplifier(onoff, 1);
		}
		public bool PowerAmplifier(string onoff, byte antenna)
		{
			byte[] off = { 0x00, 0x01, 0x61, 0x07, 0x46 };
			byte[] on = { 0x00, 0x02, 0x60, 0x01, 0x46, 0x28 };
			byte[] ack = new byte[6];

			try
			{
				switch (onoff)
				{
					case "on":
						on[3] = antenna;
						Checksum(on, 4);
						networkstream.Write(on, 0, on.Length);
						ack = GivemeWhatINeed(0x60);
						return ack[3] == 0x00 ? true : false;
					case "off":
						networkstream.Write(off, 0, off.Length);
						networkstream.Write(off, 0, off.Length);
						clear();
						networkstream.Write(off, 0, off.Length);
						ack = GivemeWhatINeed(0x61);
						if (ack[3] == 0)
						{
							loop = false;
							return true;
						}
						break;
				}
				return false;
			}
			catch (Exception ex)
			{
				throw ex;
			}

		}
		/* Give me or die. */
		private byte[] GivemeWhatINeed(byte mustbe)
		{
			// TODO: Usar buff[0] e buff[1] para o calculo do size
			byte[] header = new byte[3];
			byte[] buff = { 0xFF, 0xFF, 0xFF, 0xFF };
			byte[] tmp;
			int size = 0;
			long cnt = 0;
			try
			{
				do
				{
					if (networkstream.DataAvailable)
					{
						networkstream.Read(header, 0, 3);
						size = header[1] + 1;
						tmp = new byte[size];
						networkstream.Read(tmp, 0, size);
						buff = new byte[header.Length + size];
						System.Buffer.BlockCopy(header, 0, buff, 0, header.Length);
						System.Buffer.BlockCopy(tmp, 0, buff, 3, tmp.Length);
						cnt = 0;
					}
					else
					{
						cnt++;
					}
				} while (header[2] != mustbe && cnt < 3000000);
				return buff;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		private void clear()
		{
			byte[] tmp = new byte[1024];
			while (networkstream.DataAvailable)
			{
				networkstream.Read(tmp, 0, 1024);
			}
		}
		public void Checksum(byte[] data, int tamanho)
		{
			int i, cont = 0;
			int x, y, crcValue = 0;
			int aux = tamanho;

			while ((aux--) != 0)
			{
				for (i = 0x80; i != 0; i >>= 1)
				{
					crcValue = (crcValue & 0x8000) != 0 ? (crcValue << 1) ^ 0x8005 : crcValue << 1;
					if ((data[cont] & i) != 0)
						crcValue ^= 0x8005;
				}
				cont++;
			}
			crcValue &= 0xFFFF;
			y = crcValue & 0x00FF;
			x = crcValue & 0xFF00;
			x >>= 8;
			data[tamanho] = (byte)x;
			data[tamanho + 1] = (byte)y;
		}
		private double solveq(int n)
		{
			return (System.Math.Ceiling((System.Math.Log((double)n) / System.Math.Log((double)2))));
		}
	}
	/**********************/
	/* Self explain class */
	/**********************/
	internal class Misc
	{
		public static string PrintTag(byte[] tag, int size)
		{
			string hex = "";
			for (int i = 0; i < size && i < tag.Length; i++)
				hex += tag[i] < 16 ? "0" + Convert.ToString(tag[i], 16) + " " : Convert.ToString(tag[i], 16) + " ";
			return hex;
		}
		public static bool Equals(byte[] barray1, byte[] barray2)
		{
			if (barray1.Length != barray2.Length)
				return false;
			for (int i = 0; i < barray1.Length; i++)
				if (barray1[i] != barray2[i])
					return false;
			return true;
		}
		public static bool IsHexDigit(char c)
		{
			int numChar;
			int numA = Convert.ToInt32('A');
			int num1 = Convert.ToInt32('0');
			c = Char.ToUpper(c);
			numChar = Convert.ToInt32(c);
			if (numChar >= numA && numChar < (numA + 6))
				return true;
			if (numChar >= num1 && numChar < (num1 + 10))
				return true;
			return false;
		}
		public static byte[] GetBytes(string hexString, int maxbasize)
		{
			int discarded = 0;
			string newString = "";
			char c;

			hexString = hexString.Replace(" ", "");
			if (hexString.Length % 2 != 0)
				hexString = "0" + hexString;
			for (int i = 0; i < hexString.Length; i++)
			{
				c = hexString[i];
				if (IsHexDigit(c) && i < (maxbasize * 2))
					newString += c;
				else
					discarded++;
			}
			if (newString.Length % 2 != 0)
			{
				discarded++;
				newString = newString.Substring(0, newString.Length - 1);
			}
			int byteLength = newString.Length / 2;
			byte[] bytes = new byte[byteLength];
			string hex;
			int j = 0;
			for (int i = 0; i < bytes.Length; i++)
			{
				hex = new String(new Char[] { newString[j], newString[j + 1] });
				bytes[i] = HexToByte(hex);
				j += 2;
			}
			return bytes;
		}
		private static byte HexToByte(string hex)
		{
			if (hex.Length > 2 || hex.Length <= 0)
				throw new ArgumentException("hex must be 1 or 2 characters in length");
			byte newByte = byte.Parse(hex, System.Globalization.NumberStyles.HexNumber);
			return newByte;
		}
	}
}
